""" 

	Utils used in RepresentedBy Facebook application
	
	http://apps.facebook.com/representedby/
    
"""

__author__ = "Will Turnage (will@wubbahed.com)"
__version__ = "0.1.0"
__copyright__ = "Copyright (c) 2009 wubbahed.com"
__license__ = "GPLv3"

import const
import string
import math
import facebook
import urllib

from xml.dom import minidom
from datetime import datetime
from datetime import time
from django.utils import simplejson
from google.appengine.ext import db
from google.appengine.api import memcache
from google.appengine.api import urlfetch
from google.appengine.api.urlfetch_errors import DownloadError


# data table that stores user's legislators
class UserDistrict(db.Model):
    uid = db.IntegerProperty(required=True)
    type = db.StringProperty(required=True, choices=set(["facebook"]))
    district = db.IntegerProperty()
    state = db.StringProperty()
    rep = db.StringProperty()
    jrsen = db.StringProperty()
    srsen = db.StringProperty()
    created = db.DateTimeProperty(auto_now_add=True)
    lastvisit = db.DateTimeProperty(auto_now=True)

# data table that stores user's likes and dislikes   
class UserLikes(db.Model):
    uid = db.IntegerProperty(required=True)
    legislator = db.StringProperty(required=True)
    like = db.BooleanProperty(required=True, default=True)
    created = db.DateTimeProperty(auto_now_add=True)
    updated = db.DateTimeProperty(auto_now=True)

# data table that stores if a user has pledged to support transparency
class Pledges(db.Model):
    uid = db.IntegerProperty()
    legislator = db.StringProperty()
    type = db.StringProperty(required=True, choices=set(["user", "legislator"]))
    count = db.IntegerProperty(default=0)
    created = db.DateTimeProperty(auto_now_add=True)
    updated = db.DateTimeProperty(auto_now=True)
    
# data table that stores challenge information
class Challenges(db.Model):
    challenger = db.IntegerProperty()
    challengee = db.IntegerProperty()
    completed = db.BooleanProperty(required=True, default=False)
    challengeescore = db.IntegerProperty(default=0)
    challengerscore = db.IntegerProperty(default=0)
    created = db.DateTimeProperty(auto_now_add=True)
    updated = db.DateTimeProperty(auto_now=True)
    
#data table that stores the overall leaderboard for the challenges
class Leaderboard(db.Model):
    uid = db.IntegerProperty()
    score = db.IntegerProperty(default=0)
    created = db.DateTimeProperty(auto_now_add=True)
    updated = db.DateTimeProperty(auto_now=True)

#data table used to redirect new users into the quiz
class EntryPoint(db.Model):
    uid = db.IntegerProperty(required=True)
    section = db.StringProperty(required=True)
    
    
#utilities for getting/storing info from the datastore
class dbUtil():
    
    #stores user's legislators in the datastore   
	def setUserDistrict(self, row):
		if row.has_key("uid"):
			newUser = UserDistrict.get_or_insert("facebook" + str(row["uid"]), uid=int(row["uid"]), type="facebook")
			if row.has_key("rep"):
				newUser.rep = row["rep"]
			if row.has_key("jrsen"):
				newUser.jrsen = row["jrsen"]
			if row.has_key("srsen"):
				newUser.srsen = row["srsen"]
			if row.has_key("state"):
				newUser.state = row["state"]
			if row.has_key("district"):   
				newUser.district = int(row["district"]) 
			newUser.put()  
					
	# gets legislators for a user from the datastore
	def getLegislatorForUser(self, uid, current_location=None, refresh=False):
	    
	    # attempt to get the user from the database
	    row = None
	    query = UserDistrict.all()
	    query.filter("uid =", int(uid))
	    result = query.fetch(1)
	    keys = ["rep", "jrsen", "srsen", "state", "district"]
	    sunlight = SunlightUtil(const.SUNLIGHT_API_KEY)
	        
	    if len(result) == 0:
	        row = sunlight.getDistrictForUser(uid, current_location)
	        if row is not None:
				# if the user gives location and they're in the US
				row = sunlight.getLegislatorsFromLocationRow(row)
				# if you want the info saved to the database, then do it here
				if refresh:
					self.setUserDistrict(row)
					        
	    else:
	        row = {"uid" : uid }
	        if result[0].district is not None:
	            row["district"] = result[0].district
	        if result[0].state:
	            row["state"] = result[0].state
	        if refresh:
				row = sunlight.getLegislatorsFromLocationRow(row)
				self.setUserDistrict(row)
	        else:
		        if result[0].rep:
		            row["rep"] = result[0].rep
		        if result[0].jrsen:
		            row["jrsen"] = result[0].jrsen
		        if result[0].srsen:
		            row["srsen"] = result[0].srsen
	    return row
	   
	   
#utilities for interacting with the facebook API	   
class fbUtil():
	
	# called from every page to see if user is logged in and a valid app user
	def checkFacebookAppInstalled(self, output, write=True, section=None):
		facebookapi = facebook.Facebook(const.FACEBOOK_API_KEY, const.FACEBOOK_SECRET_KEY)
		if facebookapi.check_session(output.request):
			pass
		else:
			if write:
				self.saveAppEntryPoint(facebookapi.uid, section)
				url = facebookapi.get_add_url()
				output.response.out.write('<fb:redirect url="' + url + '" />')
			return None
		   
		if facebookapi.added:
			pass
		else:
			if write:
				self.saveAppEntryPoint(facebookapi.uid, section)
				url = facebookapi.get_add_url()
				output.response.out.write('<fb:redirect url="' + url + '" />')
			return None
		
		return facebookapi
    
    # saves user's entry point into the data store   
	def saveAppEntryPoint(self, uid=None, sectionName=None):
		if (uid is not None) and (sectionName is not None):
			query = EntryPoint.get_or_insert("user"+str(uid), uid=int(uid), section=str(sectionName))
    
    # gets user's entry point from the data store (if it exists)
	def getRedirect(self, uid=None):
		redirect = None
		if uid is not None:
			query = EntryPoint.all()
			query.filter("uid =", int(uid))
			results = query.fetch(1)
			for result in results:
				redirect = result.section
				result.delete()
		return redirect
			
#utilities for getting/storing info from the memcache system			   		
class memcacheUtil():
	
	def get(self, key):
		return memcache.get(key)
	
	def set(self, key, value=None, expire=86400):
		memcache.add(key, value, expire)
	
	# used for getting/setting raw text in memcache	
	def getTextFromCache(self, url, expire=86400):
	    content = memcache.get(url)
	    if content is None:
			try:
			    result = urlfetch.fetch(url)
			    content = result.content
			    memcache.add(url, content, expire)
			except:
				content = None
	    return content

	# used for converting RSS feeds into native dict objects, then storing in memcache		   
	def getJSONFromRSSCache(self, url, expire=86400, output=None):
		content = memcache.get(url)
		if content is None:
			try:
				result = urlfetch.fetch(url)
				xmlobj = minidom.parseString(result.content)
				content = []
				rssItems = xmlobj.getElementsByTagName("item")
				rssKeys = ["title", "link", "pubDate"]
				for item in rssItems:
					tempDict = {}
					for key in rssKeys:
						tempDict[key] = self.getTextFromXMLNode(item.getElementsByTagName(key)[0].childNodes, output)
					content.append(tempDict)        		
				memcache.add(url, content, expire)
			except:
				content = None  
		return content

	# used for getting text out of an XML node		
	def getTextFromXMLNode(self, nodelist, output=None):
		rc = ""
		for node in nodelist:
			if output is not None:
				output.response.out.write(str(node.nodeType) + " " + node.data + "\n")
			if (node.nodeType == node.TEXT_NODE) or (node.nodeType == node.CDATA_SECTION_NODE):
				rc = rc + node.data
		return rc

	# used for getting/setting dict objects from JSON objects		
	def getJSONFromCache(self, url, response="response", expire=86400, output=None, headers=None):
		if output is not None:
			output.response.out.write(url)	
		content = memcache.get(url)
		if content is None:
			try:
				result = {}
				if headers is None:
					result = urlfetch.fetch(url)
				else:
					result = urlfetch.fetch(url, headers=headers)
					
				content = simplejson.loads(result.content)
				if response is not None:
					memcache.add(url, content[response], expire)
					content = content[response]
				else:
					memcache.add(url, content, expire)	    	
			except DownloadError:
				return None
			except ValueError:
			    if output is not None:
				    output.response.out.write("<br/>error - deleted!")
			    memcache.delete(url)
		else:
			if output is not None:
				output.response.out.write("<br/>from cache: ")
				output.response.out.write(content)
		return content
	
class textUtil():
 
 	# converts dates and times in Facebook friendly times (e.g. 4 days ago)
	def timeAgo(self, theDate, theTime, nativeTime=None):
	    #utility for converting timestamps into user friendly Facebook-esque timestamps
	    result = ""
	    now = datetime.now()
	    then = datetime.now()
	    if nativeTime is None:
	    	then = datetime.strptime(theDate + " " + theTime, "%Y-%m-%d %H:%M:%S")
	    else:
	    	then = nativeTime
	    	
	    delta = now - then
	    diff = int((delta.days * 86400) + delta.seconds)     
	    if (diff < 60):        
	        if(diff <> 1):
	            result = str(diff) + " seconds ago"
	        else:
	            result = str(diff) + " seconds ago"
	    else:
	        diff = int(round(diff / 60, 0));        
	        if (diff < 60):
	            if(diff <> 1):
	                result = str(diff) + " minutes ago"
	            else:
	                result = str(diff) + " minute ago"
	        else:
	            diff = int(round(diff / 60, 0))
	            if (diff < 24):
	                if(diff <> 1):
	                    result = str(diff) + " hours ago"
	                else:
	                    result = str(diff) + " hour ago"
	            else:
	                diff = int(round(diff / 24, 0))
	                if (diff < 7):
	                    if(diff <> 1):
	                        result = str(diff) + " days ago"
	                    else:
	                        result = str(diff) + " day ago"
	                else:
	                    diff = int(round(diff / 7, 0))
	                    if(diff <> 1):
	                        result = str(diff) + " weeks ago"
	                    else:
	                        result = str(diff) + " week ago"
	    return result
	
	
	    
       
# utilities for interacting with Sunlight's API						
class SunlightUtil():

	def __init__(self, key=None):
		self.apikey = key

	# generic API handler	
	def apicall(self, func, params={}):
		result = None
		if self.apikey is not None:
			url = 'http://services.sunlightlabs.com/api/%s.json?apikey=%s&%s' % (func, self.apikey, urllib.urlencode(params))
			result = memcacheUtil().getJSONFromCache(url)	
		return result
	
	# get specific district for a user from the API including checks for the US	def getDistrictForUser(self, uid, current_location=None):
		
		row = None
		if current_location is not None:
			if current_location.has_key("country") :
				if current_location["country"] == "United States" :
					row = {"uid" : uid}
					if current_location.has_key("zip"):
						if len(current_location["zip"]):
							districts = self.apicall('districts.getDistrictsFromZip', {'zip':current_location["zip"]})
							if districts is not None:
								if districts.has_key("districts"):
									if len(districts["districts"]) == 1:
										row["state"] = districts["districts"][0]["district"]["state"]
										row["district"] = districts["districts"][0]["district"]["number"]
									
					if not row.has_key("state"):
						if current_location.has_key("state"):
							row["state"] = const.STATES[string.upper(current_location["state"])]
		return row
	 
	# get specific district for a user based on location data returned from Facebook 
	def getLegislatorsFromLocationRow(self, row):
		if row.has_key("district"):
			# we get rep and senator info
			legislators = self.apicall('legislators.getList', {"state": row["state"], "district": row["district"]})
			if legislators is not None:
				if legislators.has_key("legislators"):
					for legislator in legislators["legislators"]:
						row["rep"] = legislator["legislator"]["bioguide_id"]
						
		if row.has_key("state"):
			# we only get senators
			senators = self.apicall('legislators.getList', {"title": "Sen", "state":row["state"]})
			if senators is not None:
				if senators.has_key("legislators"):
					for senator in senators["legislators"]:
					    if (senator["legislator"]["district"] == "Senior Seat"):
					    	row["srsen"] = senator["legislator"]["bioguide_id"]
					    if (senator["legislator"]["district"] == "Junior Seat"):
					    	row["jrsen"] = senator["legislator"]["bioguide_id"]
				    	
		return row
	
	
# utilities for interacting with the OpenSecret API						
class OpenSecretsUtil():

	def __init__(self, key=None):
		self.apikey = key
		
	# generic API handler			
	def apicall(self, params):
		result = None
		if self.apikey is not None:
			url = 'http://www.opensecrets.org/api/?output=json&apikey=%s&%s' % (self.apikey, urllib.urlencode(params))
			result = memcacheUtil().getJSONFromCache(url)	
		return result
	
# utilities for interacting with NYTimes Congress API		 				
class NYTimesUtil():

	def __init__(self, key=None):
		self.apikey = key
		
	# generic API handler			
	def apicall(self, legislator):
		result = None
		if self.apikey is not None:
			url = 'http://api.nytimes.com/svc/politics/v2/us/legislative/congress/members/%s/votes.json?api-key=%s' % (legislator, self.apikey)
			result = memcacheUtil().getJSONFromCache(url, "results")	
		return result
	